#pragma once
#ifndef __HWINCOM_H__
#define __HWINCOM_H__

#include "hwindef.h"
#include "hwinexception.h"
#include "hwinlog.h"

namespace harlinn
{
    namespace windows
    {
        class Guid : public GUID
        {
        public:
            static int Compare(const GUID& first, const GUID& second)
            {
                HWIN_TRACE();
                return memcmp(&first, &second, sizeof(GUID));
            }

            Guid()
            {
                HWIN_TRACE();
                memset(&Data1,0,sizeof(GUID));
            }

            Guid(const GUID& other)
            {
                HWIN_TRACE();
                memcpy(&Data1,&other.Data1,sizeof(GUID));
            }

            Guid(const Guid& other)
            {
                HWIN_TRACE();
                memcpy(&Data1,&other.Data1,sizeof(GUID));
            }

            static Guid NewGuid()
            {
                HWIN_TRACE();
                Guid result;
                CoCreateGuid(&result);
                return result;
            }

            int CompareTo(const GUID& other) const
            {
                HWIN_TRACE();
                return Compare(*this,other);
            }

            bool operator == (const GUID& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) == 0; 
            }
            bool operator != (const GUID& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) != 0; 
            }
            bool operator <= (const GUID& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) <= 0; 
            }
            bool operator <  (const GUID& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) <  0; 
            }
            bool operator >  (const GUID& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) >  0; 
            }
            bool operator >= (const GUID& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) >= 0; 
            }

            bool operator == (const Guid& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) == 0; 
            }
            bool operator != (const Guid& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) != 0; 
            }
            bool operator <= (const Guid& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) <= 0; 
            }
            bool operator <  (const Guid& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) <  0; 
            }
            bool operator >  (const Guid& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) >  0; 
            }
            bool operator >= (const Guid& other) const 
            { 
                HWIN_TRACE();
                return CompareTo(other) >= 0; 
            }

            Guid& operator = (const GUID& other)
            {
                HWIN_TRACE();
                memcpy(&Data1,&other.Data1,sizeof(GUID));
                return *this;
            }

            Guid& operator = (const Guid& other)
            {
                HWIN_TRACE();
                memcpy(&Data1,&other.Data1,sizeof(GUID));
                return *this;
            }


        };


        class Unknown
        {
        protected:
            IUnknown* unknown;
        public:
            typedef IUnknown InterfaceType;

            Unknown()
                : unknown(nullptr)
            {
                HWIN_TRACE();
            }

            explicit Unknown(IUnknown* theUnknown, bool addref = false)
                : unknown(theUnknown)
            {
                HWIN_TRACE();
                if( addref && (unknown != nullptr))
                {
                    unknown->AddRef();
                }
            }

            Unknown(REFIID iid, const Unknown& theUnknown, bool throwIfNoInterface = true )
                : unknown(nullptr)
            {
                HWIN_TRACE();
                if( theUnknown )
                {
                    IUnknown* pInterface = nullptr;
                    auto hr = theUnknown.unknown->QueryInterface(iid,(void**)&pInterface);
                    if(FAILED(hr))
                    {
                        if((throwIfNoInterface == false)&&(hr == E_NOINTERFACE))
                        {
                            return;
                        }
                        CheckHRESULT(hr);
                    }
                    unknown = pInterface;
                }
            }

            Unknown(const Unknown& other)
                : unknown(other.unknown)
            {
                HWIN_TRACE();
                if(unknown)
                {
                    unknown->AddRef();
                }
            }

            Unknown(Unknown&& other)
                : unknown(0)
            {
                HWIN_TRACE();
                if(other.unknown)
                {
                    unknown = other.unknown;
                    other.unknown = nullptr;
                }
            }


            ~Unknown()
            {
                HWIN_TRACE();
                IUnknown* tmp = unknown;
                unknown = nullptr;
                if(tmp)
                {
                    tmp->Release();
                }
            }

            operator bool() const
            {
                HWIN_TRACE();
                return unknown != nullptr;
            }

            Unknown& operator = (const Unknown& other)
            {
                HWIN_TRACE();
                if(unknown != other.unknown)
                {
                    if(unknown)
                    {
                        IUnknown* tmp = unknown;
                        unknown = nullptr;
                        tmp->Release();
                    }
                    unknown = other.unknown;
                    if(unknown)
                    {
                        unknown->AddRef();
                    }
                }
                return *this;
            }

            Unknown& operator = (Unknown&& other)
            {
                HWIN_TRACE();
                if (this != &other)
                {
                    IUnknown* tmp = unknown;
                    unknown = nullptr;
                    if(tmp)
                    {
                        tmp->Release();
                    }
                    unknown = other.unknown;
                    other.unknown = nullptr;
                }
                return *this;
            }

            Unknown& Reset(IUnknown* other = nullptr, bool addRef = false)
            {
                HWIN_TRACE();
                if(unknown != other)
                {
                    if(unknown)
                    {
                        IUnknown* tmp = unknown;
                        unknown = nullptr;
                        tmp->Release();
                    }
                    unknown = other;
                    if( addRef && (unknown != nullptr))
                    {
                        unknown->AddRef();
                    }
                }
                return *this;
            }

            template<typename T>
            T As() const
            {
                HWIN_TRACE();
                const Unknown& self = *this;
                T result(self,false);
                return result;
            }

            template<typename T>
            bool Is() const
            {
                HWIN_TRACE();
                if(unknown)
                {
                    T::InterfaceType* pInterface = nullptr;
                    auto hr = unknown->QueryInterface(__uuidof(T::InterfaceType),(void**)&pInterface);
                    if(hr == S_OK )
                    {
                        pInterface->Release();
                        return true;
                    }
                }
                return false;
            }

            template<typename T>
            T* GetInterfacePointer() const
            {
                HWIN_TRACE();
                if(unknown)
                {
                    return reinterpret_cast<T*>(unknown);
                }
                return nullptr;
            }



            template<typename T>
            static T CoCreateInstanceFromClassId(const CLSID& clsid, DWORD classContext = CLSCTX_INPROC_SERVER)
            {
                HWIN_TRACE();
                T::InterfaceType* result = nullptr;
                auto hr = CoCreateInstance(clsid,NULL,classContext,__uuidof(T::InterfaceType),(void**)&result);
                CheckHRESULT(hr);
                return T(result);
            }

            template<typename T>
            static T CoCreateInstanceFromClassId(const String& clsid, DWORD classContext = CLSCTX_INPROC_SERVER)
            {
                HWIN_TRACE();
                CLSID CLSID_ = {0,};
                auto hr = CLSIDFromString(clsid.c_str(),&CLSID_);
                CheckHRESULT(hr);
                T::InterfaceType* result = nullptr;
                hr = CoCreateInstance(CLSID_,NULL,classContext,__uuidof(T::InterfaceType),(void**)&result);
                CheckHRESULT(hr);
                return T(result);
            }

            template<typename T>
            static T CoCreateInstanceFromProgId(const String& progId, DWORD classContext = CLSCTX_INPROC_SERVER)
            {
                HWIN_TRACE();
                CLSID CLSID_ = {0,};
                auto hr = CLSIDFromProgIDEx(clsid.c_str(),&CLSID_);
                CheckHRESULT(hr);
                T::InterfaceType* result = nullptr;
                hr = CoCreateInstance(CLSID_,NULL,classContext,__uuidof(T::InterfaceType),(void**)&result);
                CheckHRESULT(hr);
                return T(result);
            }



        };

#define HARLINN_WINDOWS_COM_STANDARD_METHODS_IMPL( classType, baseClassType , interfaceType, baseInterfaceType ) \
public: \
typedef interfaceType InterfaceType; \
typedef baseInterfaceType BaseInterfaceType; \
private: \
 InterfaceType * GetInterface() const { HWIN_TRACE();if(!unknown) { ThrowNoInterface(); } return reinterpret_cast< InterfaceType *>( unknown ); } \
public: \
classType () \
 : baseClassType ( ) \
 {} \
explicit classType (interfaceType * theInterface, bool addref = false ) \
 : baseClassType ( reinterpret_cast< baseInterfaceType *>( theInterface ), addref ) \
 { HWIN_TRACE(); } \
protected: \
classType (REFIID iid, const Unknown& theUnknown, bool throwIfNoInterface = true ) \
 : baseClassType ( iid, reinterpret_cast< const baseClassType & > (theUnknown), throwIfNoInterface ) \
 { HWIN_TRACE(); } \
public: \
classType ( const Unknown& theUnknown, bool throwIfNoInterface = true ) \
 : baseClassType ( __uuidof(interfaceType), reinterpret_cast< const baseClassType & > (theUnknown), throwIfNoInterface ) \
 { HWIN_TRACE(); } \
classType (const classType & other) \
 : baseClassType ( reinterpret_cast< const baseClassType & > (other) ) \
 { HWIN_TRACE(); } \
classType (classType && other) \
 : baseClassType ( reinterpret_cast< baseClassType && >( other ) ) \
 { HWIN_TRACE(); } \
classType & operator = (const classType & other) \
 { \
   HWIN_TRACE(); \
   baseClassType :: operator = ( reinterpret_cast< const baseClassType & > (other) ); \
   return *this; \
 } \
classType & operator = (classType && other) \
 { \
   HWIN_TRACE(); \
   baseClassType :: operator = ( reinterpret_cast< baseClassType && > (other) ); \
   return *this; \
 } \
operator interfaceType * ( ) const\
 { \
   HWIN_TRACE(); \
   return reinterpret_cast< interfaceType * >( unknown ); \
 }

        template<typename T> 
        class UnknownPtr : public Unknown
        {
        public:
            typedef Unknown Base;

            UnknownPtr()
                : Base(nullptr)
            {
                HWIN_TRACE();
            }

            explicit UnknownPtr(T* unknown)
                : Base(unknown)
            {
                HWIN_TRACE();
            }

            UnknownPtr(const UnknownPtr& other)
                : Base(other)
            {
                HWIN_TRACE();
            }

            UnknownPtr(UnknownPtr&& other)
                : Base(other)
            {
                HWIN_TRACE();
            }


            UnknownPtr& operator = (const UnknownPtr& other)
            {
                HWIN_TRACE();
                Base::operator=(other);
                return *this;
            }

            UnknownPtr& operator = (UnknownPtr&& other)
            {
                HWIN_TRACE();
                Base::operator=(other);
                return *this;
            }


            T* operator -> () const 
            { 
                HWIN_TRACE();
                return (T*)unknown; 
            }
        };


        template< class InterfaceType >
        class IUnknownImplementation : public InterfaceType
        {
            ULONG referenceCount;
        public:
            IUnknownImplementation()
                : referenceCount(1)
            {
                HWIN_TRACE();
            }

            virtual ~IUnknownImplementation()
            {
                HWIN_TRACE();
            }


            virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, void ** ppvObject )
            {
                HWIN_TRACE();
                if (!ppvObject)
                {
                    return E_INVALIDARG;
                }
                *ppvObject = NULL;
                if (riid == IID_IUnknown )
                {
                    IUnknown* pInterface = this;
                    *ppvObject = (LPVOID)pInterface;
                    AddRef();
                    return NOERROR;
                }
                else if(riid == __uuidof(InterfaceType))
                {
                    InterfaceType* pInterface = this;
                    *ppvObject = (LPVOID)pInterface;
                    AddRef();
                    return NOERROR;
                }
                return E_NOINTERFACE;
            }

            virtual ULONG STDMETHODCALLTYPE AddRef( void )
            {
                HWIN_TRACE();
                ULONG result = InterlockedIncrement(&referenceCount);
                return result;
            }

            virtual ULONG STDMETHODCALLTYPE Release( void )
            {
                HWIN_TRACE();
                ULONG result = InterlockedDecrement(&referenceCount);
                if (result == 0)
                {
                    delete this;
                }
                return result;
            }
        };


        class EnumString : public Unknown
        {
        public:
            typedef Unknown Base;

            HARLINN_WINDOWS_COM_STANDARD_METHODS_IMPL(EnumString,Unknown,IEnumString,IUnknown)

            HWIN_EXPORT bool Next( String& theResult );
            HWIN_EXPORT bool Next( ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched );
            HWIN_EXPORT bool Next( ULONG celt, const std::shared_ptr< std::vector<String> >& strings);
            HWIN_EXPORT std::shared_ptr< std::vector<String> > All();
            HWIN_EXPORT EnumString& Skip( ULONG celt );
            HWIN_EXPORT EnumString& Reset( );
            HWIN_EXPORT EnumString Clone( );
        
        };



    };
};

#endif //__HWINCOM_H__
